package st.mark.quadtris.states;

import java.util.ArrayList;
import java.util.Random;

import org.newdawn.slick.AngelCodeFont;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.Sound;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;

import st.mark.quadtris.GameInfoData;
import st.mark.quadtris.MusicManager;
import st.mark.quadtris.Piece;
import st.mark.quadtris.PieceFactory;
import st.mark.quadtris.Pit;
import st.mark.quadtris.Quadtris;
import st.mark.quadtris.Piece.Tuple;

public class GamePlay extends BasicGameState {

	// GamePlay State ID
	private int STATE_ID = -1;
	
	// Random generator
	private Random rnd = new Random();
	
	// Mini State Machine
	private enum STATES {
        START_GAME_STATE, NEW_PIECE_STATE, MOVING_PIECE_STATE, LINE_DESTRUCTION_STATE,
        PAUSE_GAME_STATE, GAME_OVER_STATE
    }
	
	private STATES current_state = null;
	
	// Objects to deal with creating new pieces and the pits
	private PieceFactory pf = null;
	private Pit[] pits = new Pit[4]; // pit 0 = bottom       pit 1 = top       pit 2 = left     pit 3 = right
	
	// Which pit to use
	private int piece_direction = 0;
	
	// Images to be used
	private Image bg = null;
	private Image[] arrows = new Image[4];
	private Image shapes = null;
	private Image allBlocks = null;
	private ArrayList<Image> blockImages = null;
	
	private Image loading_hiscores = null;
	
	// Variable to get the size of a single block
	private int block_size = 12;
	
	// Score and Statistics
	private int score = 0;
	private int level = 1;
	private int lines = 0;
	private int lines_1 = 0, lines_2 = 0, lines_3 = 0, lines_4 = 0;
	
	// Control Rotation Speed and Input
	private int delta_counter_init = 410;
	private int delta_counter = delta_counter_init;
	private int input_delta_init = 100;
    private int input_delta = 0;
	
	// Block Info
	private Tuple cursor_pos;
    private Piece current_piece = null;
    private Piece[] next_piece = new Piece[4];
    
    private Piece hold_piece = null;
    private boolean hold_swapped = false;
    private boolean rotate_key_pressed = false;
    
    // Font for scores, lines, levels etc
    private AngelCodeFont font = null;
	
    
	// Paused Menu Options
	private Image pause_bg = null;
	private Image options[] = new Image[2];
	private Image sliders = null;
	private Image slider_bar = null;
    private Image option_pointer = null;
    private float pointer_x = 0;
    private int pointer_x_max = 30;
    private int selected_option = 0;
    private boolean pointer_dir = false;
    
    private Sound click = null;
    private int last_selected = 0;
    
    private Sound rotate = null;
    private Sound piece_move = null;
    private Sound piece_down = null;
    private Sound line_noise = null;

	public GamePlay(int state){ this.STATE_ID = state; }
	
	public int getID() { return STATE_ID; }

	public void enter(GameContainer gc, StateBasedGame sbg){
		GameInfoData.SHOW_SHAPES = false;
		reset_game();
	}
	
	public void init(GameContainer gc, StateBasedGame sbg) throws SlickException {
		// Dealing with pieces
		pf = new PieceFactory();
		
		// Loading images
		bg = new Image("st/mark/resources/game/bg.png");
		
		arrows[0] = new Image("st/mark/resources/game/arrow_down.png");
		arrows[1] = new Image("st/mark/resources/game/arrow_up.png");
		arrows[2] = new Image("st/mark/resources/game/arrow_left.png");
		arrows[3] = new Image("st/mark/resources/game/arrow_right.png");
		
		shapes = new Image("st/mark/resources/game/shapes.png");
		
		allBlocks = new Image("st/mark/resources/game/blocks.png");
		
		blockImages = new ArrayList<Image>();
		for(int i = 0; i < 7; i++)
            blockImages.add(allBlocks.getSubImage(0, i*12, 12, 12));
		
		// load font
		font = new AngelCodeFont("st/mark/resources/game/font.fnt", "st/mark/resources/game/font.png");
		
		// load pause menu images
		pause_bg = new Image("st/mark/resources/pausemenu/paused_menu.png");
		options[0] = new Image("st/mark/resources/pausemenu/resume.png");
		options[1] = new Image("st/mark/resources/pausemenu/end_game.png");
		sliders = new Image("st/mark/resources/pausemenu/sliders.png");
		slider_bar = new Image("st/mark/resources/pausemenu/slider_bar.png");
		option_pointer = new Image("st/mark/resources/pausemenu/menu_pointer.png");
		
		loading_hiscores = new Image("st/mark/resources/highscores/loading.png");
		
		click = new Sound("st/mark/resources/audio/click.wav");
		rotate = new Sound("st/mark/resources/audio/rotate.wav");
		piece_down = new Sound("st/mark/resources/audio/down.wav");
		piece_move = new Sound("st/mark/resources/audio/click.wav");
		line_noise = new Sound("st/mark/resources/audio/line.wav");
	}

	public void render(GameContainer gc, StateBasedGame sbg, Graphics g) throws SlickException {
		// Draw the bg
		bg.draw(0, 0);
		
		// Render Tetris Grids
		render_pit(0, 348, 588); // Bottom
		render_pit(1, 240, 0); // Top
		render_pit(2, 240, 0); // Left
		render_pit(3, 348, 588); // Right
		
        if(gc.isPaused())
        	g.drawString("Paused!", 300, 300);
		
        // Render the Hold Piece and the Next Pieces Queue
        render_hold_next_pieces();
        
        // Render Scores and Line Counts
        render_score_lines();
        
        // Render the directional arrows
        render_arrows();
        
        // Render Pause menu
        render_pause_menu(gc);
        
        if(GameInfoData.SHOW_LOADING)
			loading_hiscores.draw(0, 0);
	}

	public void update(GameContainer gc, StateBasedGame sbg, int delta) throws SlickException {
		MusicManager.update();
		
		switch(current_state){
            case START_GAME_STATE:
                current_state = STATES.NEW_PIECE_STATE;
                delta_counter = delta_counter_init;
                break;
            case NEW_PIECE_STATE:
            	generate_new_piece();
            	hold_swapped = false;
                break;
            case MOVING_PIECE_STATE:
                update_piece( gc, sbg, delta);
                break;
            case LINE_DESTRUCTION_STATE:
            	check_for_full_lines();
                
                if(lines < 10) level = 1; 
                else if(lines < 20) { level = 2; delta_counter_init = 410; }
                else if(lines < 30) { level = 3; delta_counter_init = 390; }
                else if(lines < 40) { level = 4; delta_counter_init = 370; }
                else if(lines < 50) { level = 5; delta_counter_init = 350; }
                else if(lines < 60) { level = 6; delta_counter_init = 330; }
                else if(lines < 70) { level = 7; delta_counter_init = 310; }
                else if(lines < 80) { level = 8; delta_counter_init = 290; }
                else if(lines < 90) { level = 9; delta_counter_init = 270; }
                else if(lines < 100) { level = 10; delta_counter_init = 250; }
                else if(lines < 110) { level = 11; delta_counter_init = 230; }
                else if(lines < 120) { level = 12; delta_counter_init = 210; }
                else if(lines < 130) { level = 13; delta_counter_init = 190; }
                else if(lines < 140) { level = 14; delta_counter_init = 170; }
                else if(lines < 150) { level = 15; delta_counter_init = 150; }
                else if(lines < 160) { level = 16; delta_counter_init = 130; }
                else if(lines < 170) { level = 17; delta_counter_init = 110; }
                else if(lines < 180) { level = 18; delta_counter_init = 90; }
                else if(lines < 190) { level = 19; delta_counter_init = 70; }
                else { level = 20; delta_counter_init = 50; }
                
                current_state = STATES.NEW_PIECE_STATE;
                break;
            case PAUSE_GAME_STATE:
            	pause_menu(gc, sbg, delta);
            	
                break;
            case GAME_OVER_STATE:
                GameInfoData.HISCORE = score;
                GameInfoData.HISCORESHOW = true;
                GameInfoData.HIGAMEMODE = GameInfoData.MODE;
                GameInfoData.MODE = -1;
                GameInfoData.SHOW_LOADING = true;
                sbg.enterState(Quadtris.HIGHSCORES_STATE);
                break;
        }
	}
	
	private void generate_new_piece(){
        if(current_piece == null){
            for(int i = 0; i < next_piece.length; i++)
            	next_piece[i] = pf.generateRandomPiece();
        }
 
        current_piece = next_piece[0];
 
        // Start Piece in the middle of the top of the pit
        cursor_pos = new Tuple( 5, 21);
        
        if(pits[piece_direction].isPieceInsertableIn(current_piece, cursor_pos )){
        	
        	next_piece[0] = next_piece[1];
        	next_piece[1] = next_piece[2];
        	next_piece[2] = next_piece[3];
        	next_piece[3] = pf.generateRandomPiece();
        	
            if(GameInfoData.MODE == GameInfoData.MODE_QUAD)
            	piece_direction = rnd.nextInt(pits.length);
            else
            	piece_direction = 0;
            
            current_state = STATES.MOVING_PIECE_STATE;
            
        } else
            current_state = STATES.GAME_OVER_STATE;
 
    }

	private void update_piece( GameContainer gc, StateBasedGame sbg, int delta ){
        Tuple new_cursor_pos = new Tuple( cursor_pos.x, cursor_pos.y);
 
        //is it falling down time?
        delta_counter -= delta;
        input_delta -= delta;
        if( delta_counter < 0){
            new_cursor_pos.y -= 1;
             if(!pits[piece_direction].isPieceInsertableIn(current_piece, new_cursor_pos)){
                 pits[piece_direction].insertPieceAt(current_piece, (int)cursor_pos.x, (int)cursor_pos.y);
                 
                 if(!piece_down.playing())
                	 piece_down.play();
                 
                 current_state = STATES.LINE_DESTRUCTION_STATE;
                 return;
             }
 
            delta_counter = delta_counter_init;
        }
        
        Input input = gc.getInput();
        if(input_delta < 0){
            
        	
        	
        	if(input.isKeyDown(Input.KEY_LEFT)){
                new_cursor_pos.x -= 1;
                if(!pits[piece_direction].isPieceInsertableIn(current_piece, new_cursor_pos))
                    new_cursor_pos.x += 1;
                else {
                    input_delta = input_delta_init;
                    if(!piece_move.playing())
                    	piece_move.play();
                }
            }
            
            if(input.isKeyDown(Input.KEY_RIGHT)){
                new_cursor_pos.x += 1;
                if(!pits[piece_direction].isPieceInsertableIn(current_piece, new_cursor_pos))
                    new_cursor_pos.x -= 1;
                else {
                    input_delta = input_delta_init;
                    if(!piece_move.playing())
                    	piece_move.play();
                }
            }
            
            if(input.isKeyDown(Input.KEY_UP) && !rotate_key_pressed){
            	rotate_key_pressed = true;
                current_piece.rotateRight();
                
                if(!pits[piece_direction].isPieceInsertableIn(current_piece, new_cursor_pos)){
                	
                	if(new_cursor_pos.x == 0){
                		// left side
                		if(!pits[piece_direction].isPieceInsertableIn(current_piece, new Tuple(1,new_cursor_pos.y)))
                			current_piece.rotateLeft();
                		else 
                			new_cursor_pos.x = 1;
                	} else if(new_cursor_pos.x == pits[piece_direction].width-1){
                		// right side
                		if(current_piece.getColor() != 4){
	                		if(!pits[piece_direction].isPieceInsertableIn(current_piece, new Tuple(pits[piece_direction].width-2,new_cursor_pos.y)))
	                			current_piece.rotateLeft();
	                		else 
	                			new_cursor_pos.x = pits[piece_direction].width-2;
                		} else {
                			if(!pits[piece_direction].isPieceInsertableIn(current_piece, new Tuple(pits[piece_direction].width-3,new_cursor_pos.y)))
	                			current_piece.rotateLeft();
	                		else 
	                			new_cursor_pos.x = pits[piece_direction].width-3;
                		}
                	} else
                		current_piece.rotateLeft();
                	
                } else {
                    input_delta = input_delta_init;
                    rotate.play();
                }
                
            }
            
            if(!input.isKeyDown(Input.KEY_UP) && rotate_key_pressed)
            	rotate_key_pressed = false;
            
            if(input.isKeyDown(Input.KEY_DOWN)){
                new_cursor_pos.y -= 1;
                if(!pits[piece_direction].isPieceInsertableIn(current_piece, new_cursor_pos))
                    new_cursor_pos.y += 1;                    
                else 
                    input_delta = 15;
            }
            
            if(input.isKeyPressed(Input.KEY_P) || input.isKeyPressed(Input.KEY_ESCAPE)){
            	current_state = STATES.PAUSE_GAME_STATE;
            }
            
            cursor_pos = new Tuple(new_cursor_pos.x, new_cursor_pos.y);
            
            if(input.isKeyPressed(Input.KEY_SPACE) && !hold_swapped){
        		if(hold_piece != null){
        			Piece temp = current_piece;
        			current_piece = hold_piece;
        			hold_piece = temp;
        			hold_piece.resetRotation();
        		} else {
        			hold_piece = current_piece;
        			hold_piece.resetRotation();
        			current_piece = next_piece[0];
        			next_piece[0] = next_piece[1];
                	next_piece[1] = next_piece[2];
                	next_piece[2] = next_piece[3];
                	next_piece[3] = pf.generateRandomPiece();
        		}
        		hold_swapped = true;
        		cursor_pos = new Tuple( 5, 21);
        	}
        }
 
        if(GameInfoData.MODE == GameInfoData.MODE_NORM)
        	pits[1] = pits[2] = pits[3] = pits[0];
    }
	
	public void check_for_full_lines(){
        int linesDestroyed = 0;
 
        for(int lineIdx = 0; lineIdx < pits[piece_direction].getNumberOfLines(); ){
            if( pits[piece_direction].isLineFull(lineIdx) ){   
                pits[piece_direction].eraseLine(lineIdx);
                linesDestroyed++;
            } else
                lineIdx++;
        }
 
        if(linesDestroyed > 0 && !line_noise.playing())
        	line_noise.play();
        
        switch(linesDestroyed){
            case 0 : score += 10;
            break;
            case 1 : score += 100; lines++; lines_1++;
            break;
            case 2 : score += 300; lines += 2; lines_2++;
            break;
            case 3 : score += 600; lines += 3; lines_3++;
            break;
            case 4 : score += 1000; lines += 4; lines_4++;
            break;
 
        }
    }
	
	public void render_pit(int dir, int x, int y){
		
		int w = pits[dir].width, h = pits[dir].height;
		
		int reset_x = x;
		
		switch(dir){
			case 0: // bottom
			case 1: // top	
				for(int row = 0; row < h; row++){
					for(int col = w-1; col >= 0; col--){
						if(row < h-2){
							if(pits[dir].getBlockAt(col, row) != -1)
								blockImages.get(pits[dir].getBlockAt(col, row)).draw(x, y);
							
							
							if(current_piece != null && (piece_direction == dir || GameInfoData.MODE == GameInfoData.MODE_NORM))
								for(int cp = 0; cp < 4; cp++)
									if ((cursor_pos.x + current_piece.getPosOfBlock(cp).x) == col && (cursor_pos.y + current_piece.getPosOfBlock(cp).y) == row)
										blockImages.get(current_piece.getColor()).draw(x, y);
						}
						if(dir == 0)
							x -= block_size;
						else
							x += block_size;
					}
					
					x = reset_x;
					
					if(dir == 0)	
						y -= block_size;
					else
						y += block_size;
				}
				
				break;

			case 2: // left
			case 3: // right	
				for(int row = 0; row < h; row++){
					for(int col = 0; col < w; col++){
						if(row < h-2){
							if(pits[dir].getBlockAt(col, row) != -1)
								blockImages.get(pits[dir].getBlockAt(col, row)).draw(y, x);
							
							
							if(current_piece != null && (piece_direction == dir || GameInfoData.MODE == GameInfoData.MODE_NORM))
								for(int cp = 0; cp < 4; cp++)
									if ((cursor_pos.x + current_piece.getPosOfBlock(cp).x) == col && (cursor_pos.y + current_piece.getPosOfBlock(cp).y) == row)
										blockImages.get(current_piece.getColor()).draw(y, x);
						}
						
						if(dir == 2)
							x += block_size;
						else
							x -= block_size;	
					}
					x = reset_x;
					
					if(dir == 2)
						y += block_size;
					else
						y -= block_size;
				}
				
				break;
		}
	}
	
	
	public void render_arrows(){
        switch(piece_direction){
	    	case 0: arrows[0].draw(24, 24); arrows[0].draw(24, 384); break;
	    	case 1: arrows[1].draw(24, 24); arrows[1].draw(24, 384); break;
	    	case 2: arrows[2].draw(24, 24); arrows[2].draw(24, 384); break;
	    	case 3: arrows[3].draw(24, 24); arrows[3].draw(24, 384); break;
	    }
	}
	
	public void render_score_lines(){
        font.drawString(400, 75, ""+score);
        font.drawString(400, 145, ""+level);
        font.drawString(505, 392, ""+lines);
        font.drawString(545, 428, ""+lines_1);
        font.drawString(557, 464, ""+lines_2);
        font.drawString(545, 500, ""+lines_3);
        font.drawString(523, 536, ""+lines_4);
	}
	
	public void render_hold_next_pieces(){
        // Hold Piece
		if(hold_piece != null)
			shapes.getSubImage(0, hold_piece.getColor()*48, 48, 48).draw(276, 288);
		
        // Next Pieces
        int np = 0;
        for(int next = 120; next < 600; next += 120){
        	int sx = 684, sy = next;
        	
        	if(next_piece[np] != null)
        		shapes.getSubImage(0, next_piece[np].getColor()*48, 48, 48).draw(sx, sy);
	       
	        np++;
        }
	}
	
	private void reset_game(){
		// Creating the pits
		for(int i = 0; i < pits.length; i++){
			pits[i] = new Pit(10, 22);
			pits[i].makeCleanPit();
		}
		
		cursor_pos = new Tuple(5,21);
		hold_piece = null;
		// Reset Score
		score = 0;
		lines_1 = lines_2 = lines_3 = lines_4 = 0;
		
		// Set the mini state to start the game
		current_state = STATES.START_GAME_STATE;
	}
	
	private void pause_menu(GameContainer gc, StateBasedGame sbg, int delta){
		Input input = gc.getInput();
    	if(input.isKeyPressed(Input.KEY_P) || input.isKeyPressed(Input.KEY_ESCAPE)){
    		current_state = STATES.MOVING_PIECE_STATE;
    	}
    	
    	// Pointer X update
    	if(pointer_dir){
    		pointer_x += 0.05f * delta;
    		
    		if(pointer_x >= pointer_x_max){
    			pointer_dir = !pointer_dir;
    		}
    	} else {
    		pointer_x -= 0.05f * delta;
    		
    		if(pointer_x <= 0){
    			pointer_dir = !pointer_dir;
    		}
    	}
    	
    	int mx = input.getMouseX(), my = input.getMouseY();
    	
    	// Mouse Check Over Options
    	if(mx >= 266 && mx <= 266+options[0].getWidth() && my >= 205 && my <= 205+options[0].getHeight()){
    		selected_option = 0;
    		if(input.isMousePressed(Input.MOUSE_LEFT_BUTTON))
    			current_state = STATES.MOVING_PIECE_STATE;
    	}
    	
    	if(mx >= 266 && mx <= 266+options[1].getWidth() && my >= 277 && my <= 277+options[1].getHeight()){
    		selected_option = 1;
    		if(input.isMousePressed(Input.MOUSE_LEFT_BUTTON)){
    			 GameInfoData.HISCORE = score;
                 GameInfoData.HISCORESHOW = true;
                 GameInfoData.HIGAMEMODE = GameInfoData.MODE;
                 GameInfoData.MODE = -1;
                 GameInfoData.SHOW_LOADING = true;
                 sbg.enterState(Quadtris.HIGHSCORES_STATE);
    		}
    	}
    	
    	
    	// Keyboard Check for options
    	if(input.isKeyPressed(Input.KEY_UP)){
    		selected_option--;
    		
    		if(selected_option < 0)
    			selected_option = 1;
    	}
    	
    	if(input.isKeyPressed(Input.KEY_DOWN)){
    		selected_option++;
    		
    		if(selected_option > 1)
    			selected_option = 0;
    	}
    	
    	if(selected_option != last_selected){
    		click.play();
    		last_selected = selected_option;
    	}
    	
    	// Check input to do what options want to do
    	if(input.isKeyPressed(Input.KEY_ENTER)){
    		switch(selected_option){
	    		case 0: current_state = STATES.MOVING_PIECE_STATE; break;
	    		case 1:  GameInfoData.HISCORE = score;
                GameInfoData.HISCORESHOW = true;
                GameInfoData.HIGAMEMODE = GameInfoData.MODE;
                GameInfoData.MODE = -1;
                GameInfoData.SHOW_LOADING = true;
                sbg.enterState(Quadtris.HIGHSCORES_STATE); break;
    		}
    	}
    	
    	if(mx >= 111 && mx <= 111+36 && my >= 501 && my <= 501+12 && input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)){
    		gc.setSoundVolume(gc.getSoundVolume()-0.005f);
    		
    		if(gc.getSoundVolume() < 0.0f)
    			gc.setSoundVolume(0.0f);
    		
    		if(!click.playing())
    			click.play();
    	}
    	
    	if(mx >= 327 && mx <= 327+36 && my >= 489 && my <= 489+36 && input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)){
    		gc.setSoundVolume(gc.getSoundVolume()+0.005f);
    		
    		if(gc.getSoundVolume() > 1.0f)
    			gc.setSoundVolume(1.0f);
    		
    		if(!click.playing())
    			click.play();
    	}
    	
    	if(mx >= 436 && mx <= 436+36 && my >= 501 && my <= 501+12 && input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)){
    		gc.setMusicVolume(gc.getMusicVolume()-0.005f);
    		
    		if(gc.getMusicVolume() < 0.0f)
    			gc.setMusicVolume(0.0f);
    	}
    	
    	if(mx >= 652 && mx <= 652+36 && my >= 489 && my <= 489+36 && input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)){
    		gc.setMusicVolume(gc.getMusicVolume()+0.005f);
    		
    		if(gc.getMusicVolume() > 1.0f)
    			gc.setMusicVolume(1.0f);
    	}
	}
	
	public void render_pause_menu(GameContainer gc){
		 if(current_state == STATES.PAUSE_GAME_STATE){
	        	pause_bg.draw(0, 0);
	        	
	        	int x = 266;
	        	
	        	options[0].draw(x, 205);
	        	options[1].draw(x, 277);
	        	
	        	sliders.draw(111, 458);
	        	
	        	switch(selected_option){
	        		case 0: option_pointer.draw(pointer_x+200, 195); break;
	        		case 1:	option_pointer.draw(pointer_x+200, 270); break;
	        	}
	        	
	        	slider_bar.draw( (154 + (gc.getSoundVolume() * 100) * 1.55f) , 483);
	        	slider_bar.draw( (479 + (gc.getMusicVolume() * 100) * 1.55f) , 483);
	        }
	}
}
